package com.leilei.autoconfigure;

import com.leilei.support.util.J2CacheKeyUtil;
import com.leilei.support.util.SpringUtil;
import net.oschina.j2cache.CacheChannel;
import net.oschina.j2cache.J2Cache;
import net.oschina.j2cache.J2CacheBuilder;
import net.oschina.j2cache.J2CacheConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.Objects;
import java.util.Optional;
import java.util.Properties;

/**
 * @author lei
 * @create 2022-05-11 10:04
 * @desc j2cache 启动入口,基于原生starter改造
 **/
@ConditionalOnClass(J2Cache.class)
@EnableConfigurationProperties({J2CacheConfigProperties.class})
@Configuration
public class J2CacheAutoConfiguration {
    private final RedisProperties redisProperties;

    @Autowired
    public J2CacheAutoConfiguration(RedisProperties redisProperties) {
        this.redisProperties = redisProperties;
    }


    /**
     * 转换SpringBoot配置为J2Cache底层配置
     */
    @Bean
    public J2CacheConfig j2CacheConfig(J2CacheConfigProperties j2CacheConfigProperties) {
        // 设置与spring-data-redis 保持一致,进行多节点间发布订阅
        j2CacheConfigProperties.getL2().setDatabase(this.redisProperties.getDatabase());
        J2CacheConfig config = new J2CacheConfig();
        J2CacheConfigProperties.MyLevel1Config l1 = j2CacheConfigProperties.getL1();
        J2CacheConfigProperties.MyLevel2Config l2 = j2CacheConfigProperties.getL2();
        // 设置广播模式
        config.setBroadcast(j2CacheConfigProperties.getBroadcast());
        // 一级缓存生产者名
        config.setL1CacheName(l1.getProviderClass());
        // 二级缓存生产者名
        config.setL2CacheName(l2.getProviderClass());
        // 设置序列化方式
        config.setSerialization(l2.getSerialization());
        // 设置过期时间是否同步到redis
        config.setSyncTtlToRedis(l2.getSyncTtlToRedis());
        config.setDefaultCacheNullObject(j2CacheConfigProperties.isAllowNullValues());
        String l2CacheSection = l2.getL2CacheSection();
        if (l2CacheSection == null || Objects.equals(l2CacheSection.trim(), "")) {
            l2CacheSection = config.getL2CacheName();
        }
        Properties properties = j2CacheConfigProperties.getProperties();
        //转换L1缓存属性
        matchPropsPrefixAndSet(properties, config.getL1CacheProperties(), config.getL1CacheName());
        //转换L2缓存属性
        matchPropsPrefixAndSet(properties, config.getL2CacheProperties(), l2CacheSection);
        // 设置命名空间前缀  SpringRedisProvider类所需要用到
        config.getL2CacheProperties().setProperty(J2CacheKeyUtil.NAME_SPACE, l2.getNamespace());
        // 填充redis 模式 这决定了redis使用string还是hash
        config.getL2CacheProperties().setProperty(J2CacheKeyUtil.STORAGE, l2.getStorage());
        // 填充channel名字 决定多个项目实例时通信通道
        config.getL2CacheProperties().setProperty(J2CacheKeyUtil.CHANNEL, l2.getChannel());
        // 填充数据库db,与 spring-data-redis yml配置保持一致
        config.getL2CacheProperties().setProperty(J2CacheKeyUtil.DATABASE, String.valueOf(Optional.ofNullable(l2.getDatabase()).orElse(0)));
        //转换广播属性
        matchPropsPrefixAndSet(properties, config.getBroadcastProperties(), config.getBroadcast());
        // 设置caffeine region
        j2CacheConfigProperties.getL1().getCaffeine().getRegion().forEach((k, v) -> config.getL1CacheProperties().setProperty("region." + k, v));
        return config;
    }


    @Bean
    public SpringUtil springUtil() {
        return new SpringUtil();
    }

    /**
     *  实例化J2CacheBuilder  依赖于上方两个bean
     *  初始化缓存builder 内部逻辑对一二级缓存使用类实例进行了确定与处理
     *  ex: 序列化方式
     *  ex:二级缓存处理类 SpringRedisProvider.class.getName();
     *  ex:广播策略 SpringRedisPubSubPolicy.class.getName()
     * @param j2CacheConfig
     * @return
     */
    @Bean
    @DependsOn({"springUtil", "j2CacheConfig"})
    public CacheChannel cacheChannel(J2CacheConfig j2CacheConfig) {
        J2CacheBuilder builder = J2CacheBuilder.init(j2CacheConfig);
        return builder.getChannel();
    }


    /**
     * 匹配指定前缀（且删除前缀）提取J2Cache配置属性
     *
     * @param sourceProps
     * @param existsProps
     * @param prefix
     */
    private static void matchPropsPrefixAndSet(Properties sourceProps, Properties existsProps, String prefix) {
        sourceProps.forEach((k, v) -> {
            String key = (String) k;
            String value = String.valueOf(v);
            String prefixWithDot = prefix + ".";
            if (key.startsWith(prefixWithDot)) {
                existsProps.setProperty(key.substring(prefixWithDot.length()), value);
            }
        });
    }

}
